﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace DigitalImage.softLib {
    class CImageUtil {

        #region Funkcije za pretvaranje Bitmap objekta u matricu 'nijansi sive boje' i obrnuto
        public static unsafe byte[,] bitmapToByteMatrix(Bitmap src) {
            Bitmap source=new Bitmap(src);
            Rectangle lrEntire=new Rectangle(new Point(), source.Size);

            BitmapData lbdSource=source.LockBits(lrEntire, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);

            int PixelSize=4;
            int h=lbdSource.Height;
            int w=lbdSource.Width;

            byte[,] slika=new byte[h, w];
            for(int y=0;y<h;y++) {
                byte* row=(byte*)lbdSource.Scan0+(y*lbdSource.Stride);
                for(int x=0;x<w;x++) {
                    byte b=(byte)Math.Abs(row[x*PixelSize+0]);// Blue
                    byte g=(byte)Math.Abs(row[x*PixelSize+1]);// Green
                    byte r=(byte)Math.Abs(row[x*PixelSize+2]);// Red

                    byte prosek=(byte)(((double)b+(double)g+(double)r)/3.0);

                    slika[y, x]=prosek;
                }
            }
            source.UnlockBits(lbdSource);
            return slika;
        }

        public static unsafe Bitmap matrixToBitmap(byte[,] slika) {

            int w=slika.GetLength(1);// .GetLowerBound(0);
            int h=slika.GetLength(0);
            int PixelSize=4;
            Bitmap into=new Bitmap(w, h);
            Rectangle lrEntire=new Rectangle(new Point(), new Size(w, h));

            BitmapData lbdDest=into.LockBits(lrEntire, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
            for(int y=0;y<h;y++) {
                byte* rowDest=(byte*)lbdDest.Scan0+(y*lbdDest.Stride);
                for(int x=0;x<w;x++) {
                    rowDest[x*PixelSize+0]=slika[y, x];
                    rowDest[x*PixelSize+1]=slika[y, x];
                    rowDest[x*PixelSize+2]=slika[y, x];
                }
            }
            into.UnlockBits(lbdDest);
            return into;
        }
        #endregion

        #region Funkcije za pretvaranje Bitmap objekta u 'Color' matricu
        public static unsafe Bitmap colorMatrixToBitmap(byte[, ,] slika) {

            int w=slika.GetLength(1);// .GetLowerBound(0);
            int h=slika.GetLength(0);
            int PixelSize=4;
            Bitmap into=new Bitmap(w, h);
            Rectangle lrEntire=new Rectangle(new Point(), new Size(w, h));

            BitmapData lbdDest=into.LockBits(lrEntire, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
            for(int y=0;y<h;y++) {
                byte* rowDest=(byte*)lbdDest.Scan0+(y*lbdDest.Stride);
                for(int x=0;x<w;x++) {
                    rowDest[x*PixelSize+0]=slika[y, x, 2];
                    rowDest[x*PixelSize+1]=slika[y, x, 1];
                    rowDest[x*PixelSize+2]=slika[y, x, 0];
                }
            }
            into.UnlockBits(lbdDest);
            return into;
        }

        public static byte[, ,] bitmapToColorMatrix(Bitmap src) {
            Bitmap source=new Bitmap(src);
            Rectangle lrEntire=new Rectangle(new Point(), source.Size);

            BitmapData lbdSource=source.LockBits(lrEntire, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
            int h=lbdSource.Height;
            int w=lbdSource.Width;

            byte[, ,] slika=new byte[h, w, 3];
            int PixelSize=4;
            unsafe {
                for(int y=0;y<h;y++) {
                    byte* row=(byte*)lbdSource.Scan0+(y*lbdSource.Stride);
                    for(int x=0;x<w;x++) {
                        slika[y, x, 0]=row[x*PixelSize+2];// Red
                        slika[y, x, 1]=row[x*PixelSize+1];// Green
                        slika[y, x, 2]=row[x*PixelSize+0];// Blue
                    }
                }
            }
            source.UnlockBits(lbdSource);
            return slika;
        }
        #endregion

        #region Funkcije za Segmentaciju slike iz 'nijansi sive boje' u crno belo
        public static double mean(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            double mean=0;
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    mean+=slika[y, x];
                }
            }
            mean=mean/(w*h);
            return mean;
        }

        public static byte[,] matrixToBinary(byte[,] slika, byte mean) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    if(slika[y, x]<mean)
                        retVal[y, x]=0;
                    else
                        retVal[y, x]=255;
                }
            }
            return retVal;
        }

        public static byte[,] matrixToBinaryTiles(byte[,] slika, int R, int C) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            double dW=(double)w/C;
            double dH=(double)h/R;
            double[,] means=new double[R, C];
            double[,] mins=new double[R, C];
            double[,] maxs=new double[R, C];
            byte[,] retVal=new byte[h, w];

            int[] histogram=new int[255/2];
            int D=4;
            double meanDD=0;
            for(int r=0;r<R;r++) {
                for(int c=0;c<C;c++) {
                    means[r, c]=0;
                    mins[r, c]=0;
                    maxs[r, c]=0;
                    int minD=0;
                    int maxD=0;
                    int maxDif=0;
                    for(int y=0;y<dH;y++) {
                        int A=0;
                        int B=0;
                        for(int x=0;x<D;x++) {
                            A+=slika[(int)(r*dH)+y, (int)(c*dW)+x];
                        }
                        for(int x=D;x<2*D;x++) {
                            B+=slika[(int)(r*dH)+y, (int)(c*dW)+x];
                        }
                        for(int x=D;x<dW-D;x++) {
                            int diff=Math.Abs(A-B);
                            if(diff>=maxDif) {
                                maxDif=diff;
                                minD=Math.Min(A, B);
                                maxD=Math.Max(A, B);
                            }
                            A-=slika[(int)(r*dH)+y, (int)(c*dW)+x-D];
                            A+=slika[(int)(r*dH)+y, (int)(c*dW)+x];
                            B-=slika[(int)(r*dH)+y, (int)(c*dW)+x];
                            B+=slika[(int)(r*dH)+y, (int)(c*dW)+x+D-1];
                        }
                    }
                    int TT=(maxD+minD)/(2*D);
                    int DD=(maxD-minD)/D;
                    histogram[DD/2]++;
                    meanDD+=DD;
                    Console.WriteLine("DD:"+DD+"\t"+TT+"\t "+maxD+"\t");
                    for(int y=0;y<dH;y++) {
                        for(int x=0;x<dW;x++) {
                            if(DD>20) {
                                if(slika[(int)(r*dH)+y, (int)(c*dW)+x]<TT)//means[r, c])
                                    retVal[(int)(r*dH)+y, (int)(c*dW)+x]=0;
                                else
                                    retVal[(int)(r*dH)+y, (int)(c*dW)+x]=255;
                            } else {
                                if(TT<80)
                                    retVal[(int)(r*dH)+y, (int)(c*dW)+x]=0;
                                else
                                    retVal[(int)(r*dH)+y, (int)(c*dW)+x]=255;
                            }
                        }
                    }
                }
            }
            meanDD=meanDD/(R*C);
            Console.WriteLine("meanDD:"+meanDD);
            return retVal;
        }

        public static List<PointF> histogram(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            int dV=1;
            int L=(256/dV);
            int[] histogram=new int[L];
            for(int i=0;i<L;i++)
                histogram[i]=0;
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    byte b=slika[y, x];
                    int indeks=b/dV;
                    histogram[indeks]++;
                }
            }
            List<PointF> points=new List<PointF>();
            for(int i=0;i<histogram.Length;i++) {
                points.Add(new PointF(i*dV, histogram[i]));
            }
            return points;
        }
        #endregion

        #region Osnovne morfoloske operacije
        public static byte[,] erosion(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            //int[] ii= { 0, 1, 1, 1, 0, -1, -1, -1, 0 };
            //int[] jj= { 1, 1, 0, -1, -1, -1, 0, 1, 0 };
            int[] ii = { 0,  1, 0, -1, 0 };
            int[] jj = { 1,  0, -1, 0, 0 };
            int n=ii.Length;
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    Boolean b=true;
                    for(int t=0;t<n;t++) {
                        if(y+ii[t]<0||y+ii[t]>=h
                            ||x+jj[t]<0||x+jj[t]>=w)
                            continue;

                        if(slika[y+ii[t], x+jj[t]]!=0) // NIJE CRNA TACKA 
                        {
                            b=false;
                            break;
                        }
                    }
                    if(b==true)
                        retVal[y, x]=0;
                    else
                        retVal[y, x]=255;
                }
            }
            return retVal;
        }

        public static byte[,] dilation(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            //int[] ii= { 0, 1, 1, 1, 0, -1, -1, -1, 0 };
            //int[] jj= { 1, 1, 0, -1, -1, -1, 0, 1, 0 };
            int[] ii = { 0, 1, 0, -1, 0 };
            int[] jj = { 1, 0, -1, 0, 0 };
            int n=ii.Length;
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    Boolean b=false;
                    for(int t=0;t<n;t++) {
                        if(y+ii[t]<0||y+ii[t]>=h
                           ||x+jj[t]<0||x+jj[t]>=w)
                            continue;

                        if(slika[y+ii[t], x+jj[t]]==0) // BAR JEDNA CRNA TACKA 
                        {
                            b=true;
                            break;
                        }
                    }
                    if(b==true)
                        retVal[y, x]=0;
                    else
                        retVal[y, x]=255;
                }
            }
            return retVal;
        }
        #endregion

        #region Algoritam za obelezavanje regiona
        public static List<CRasterRegion> regionLabeling(byte[,] slika) {
            List<CRasterRegion> regions=new List<CRasterRegion>();
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            int[] ii= { 0, 1, 1, 1, 0, -1, -1, -1 };
            int[] jj= { 1, 1, 0, -1, -1, -1, 0, 1 };
            int n=ii.Length;
            byte regNum=0;
            for(int y=1;y<h-1;y++) {
                for(int x=1;x<w-1;x++) {
                    if(slika[y, x]==0) {
                        regNum++;
                        byte rr=(byte)(regNum*50);
                        if(rr==0)
                            rr=1;
                        slika[y, x]=rr;
                        List<Point> front=new List<Point>();
                        Point pt=new Point(x, y);
                        CRasterRegion region=new CRasterRegion();
                        region.regId=regNum;
                        region.points.Add(pt);
                        regions.Add(region);
                        front.Add(pt);
                        while(front.Count>0) {
                            Point p=front[0];
                            front.RemoveAt(0);
                            for(int t=0;t<n;t++) {
                                Point point=new Point(p.X+jj[t], p.Y+ii[t]);
                                if(point.X>-1&&point.X<w&&point.Y>-1&&point.Y<h) {
                                    byte pp=slika[point.Y, point.X];
                                    if(pp==0) {
                                        slika[point.Y, point.X]=slika[y, x];
                                        region.points.Add(point);
                                        front.Add(point);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            foreach (CRasterRegion region in regions) {
                region.odrediMomente();
            }
            return regions;
        }
        #endregion

        #region Detekcija ivica primenom Sobel operatora
        public static byte[,] iviceSobel(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] nslika=new byte[h, w];
            int[,] maskaA= {{-1, 0, 1}, 
                             {-2, 0, 2},
                             {-1, 0, 1}};
            int[,] maskaB= {{-1, -2, -1}, 
                             { 0,  0,  0},
                             { 1,  2,  1}};
            for(int y=0;y<h;y++) {
                for(int x=0;x<w;x++) {
                    nslika[y, x]=(byte)(0);
                }
            }
            for(int y=1;y<h-1;y++) {
                for(int x=1;x<w-1;x++) {
                    int sumA=0;
                    int sumB=0;
                    for(int yy=-1;yy<2;yy++)
                        for(int xx=-1;xx<2;xx++) {
                            sumA+=maskaA[yy+1, xx+1]*
                                (int)slika[y+yy, x+xx];
                            sumB+=maskaB[yy+1, xx+1]*
                                (int)slika[y+yy, x+xx];
                        }
                    double s=sumA*sumA+sumB*sumB;
                    nslika[y, x]=(byte)(Math.Sqrt(s));
                }
            }
            return nslika;
        }
        #endregion

        #region Funkcije za invertovanje slike i razlike izmedju dve slike
        public static byte[,] invert(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] nslika=new byte[h, w];
            for(int y=1;y<h-1;y++) {
                for(int x=1;x<w-1;x++) {
                    nslika[y, x]=(byte)(255-slika[y, x]);
                }
            }
            return nslika;
        }

        public static byte[,] diff(byte[,] slikaA, byte[,] slikaB) {
            int w=slikaA.GetLength(1);
            int h=slikaA.GetLength(0);
            byte[,] nslika=new byte[h, w];
            for(int y=1;y<h-1;y++) {
                for(int x=1;x<w-1;x++) {
                    nslika[y, x]=(byte)Math.Abs(slikaB[y, x]-slikaA[y, x]);
                }
            }
            return nslika;
        }
        #endregion

        // Funkcija koja radi sa metodom GetPixel
        //   puno je sporije nego kad se radi direktno sa memorijom
        public static int[,] bitmapToMatrix(Bitmap source) {
            Rectangle lrEntire=new Rectangle(new Point(), source.Size);

            int w=source.Size.Width;
            int h=source.Size.Height;
            int[,] retVal=new int[h, w];


            for(int y=0;y<h;y++) // lbdSource.Height
            {
                for(int x=0;x<w;x++) // lbdSource.Width
                {
                    Color c=source.GetPixel(x, y);

                    byte a=c.A;
                    byte b=c.B;// Blue
                    byte g=c.G;// Green
                    byte r=c.R;// Red

                    if(c.A==0) {
                        retVal[y, x]=0;
                    } else {
                        double v=(c.R+c.G+c.B)/3;
                        double hh=1-(double)v/255;
                        if(hh>0.5)
                            retVal[y, x]=1;
                        else
                            retVal[y, x]=0;
                    }
                }
            }
            return retVal;
        }

        public static byte[,] blur(byte[,] slika) {
            int w=slika.GetLength(1);
            int h=slika.GetLength(0);
            byte[,] retVal=new byte[h, w];
            int[] ii= { 0, 1, 1, 1, 0, -1, -1, -1, 0 };
            int[] jj= { 1, 1, 0, -1, -1, -1, 0, 1, 0 };
            int n=ii.Length;
            int sum=0;
            for(int y=1;y<h-1;y++) {
                for(int x=1;x<w-1;x++) {
                    sum=0;
                    for(int q=0;q<ii.Length-1;q++)
                        sum+=slika[y+ii[q], x+jj[q]];
                    sum+=slika[y, x];
                    retVal[y, x]=(byte)(sum/9);
                }
            }
            return retVal;
        }

        public static int otsu(byte[,] slika) {
            List<PointF> histF=histogram(slika);
            int[] hist=histF.Select(el => (int)el.Y).ToArray();

            int sum=0; // cela slika
            for(int i=0;i<256;i++) {
                sum+=i*hist[i];
            }

            int sumB=0; // pozadina
            int weightF=0;
            int weightB=0;

            float max=0;
            int threshold=0; // povratna vrednost
            int total=slika.GetLength(0)*slika.GetLength(1);

            for(int i=0;i<256;i++) {
                weightB+=hist[i];
                if(weightB==0)
                    continue;

                weightF=total-weightB;
                if(weightF==0)
                    break;

                sumB+=i*hist[i];

                float meanB=sumB/weightB;
                float meanF=(sum-sumB)/weightF;

                float betweenVariance=(float)weightB*(float)weightF*(float)Math.Pow(meanB-meanF, 2);

                if(betweenVariance>max) {
                    max=betweenVariance;
                    threshold=i;
                }
            }

            return threshold;
        }

        public static Bitmap MergeTwoImages(Image firstImage, Image secondImage) {
            if(firstImage==null) {
                throw new ArgumentNullException("firstImage");
            }

            if(secondImage==null) {
                throw new ArgumentNullException("secondImage");
            }

            int outputImageHeight=firstImage.Height>secondImage.Height?firstImage.Height:secondImage.Height;

            int outputImageWidth=firstImage.Width+secondImage.Width+10;

            Bitmap outputImage=new Bitmap(outputImageWidth, outputImageHeight, PixelFormat.Format32bppArgb);

            using(Graphics graphics=Graphics.FromImage(outputImage)) {
                graphics.DrawImage(firstImage, new Rectangle(new Point(), firstImage.Size),
                    new Rectangle(new Point(), firstImage.Size), GraphicsUnit.Pixel);
                graphics.DrawImage(secondImage, new Rectangle(new Point(firstImage.Width+10, 0), secondImage.Size),
                    new Rectangle(new Point(), secondImage.Size), GraphicsUnit.Pixel);
            }

            return outputImage;
        }

        public static byte[,] resizeImage(byte[,] src, Size size) {
            Bitmap imgToResize=matrixToBitmap(src);
            int sourceWidth=imgToResize.Width;
            int sourceHeight=imgToResize.Height;

            float nPercent=0;
            float nPercentW=0;
            float nPercentH=0;
            nPercentW=((float)size.Width/(float)sourceWidth);
            nPercentH=((float)size.Height/(float)sourceHeight);

            if(nPercentH<nPercentW)
                nPercent=nPercentH;
            else
                nPercent=nPercentW;

            int destWidth=(int)(sourceWidth*nPercent);
            int destHeight=(int)(sourceHeight*nPercent);
            Bitmap b=new Bitmap(size.Width, size.Height);
            int offX=(size.Width-destWidth)/2;
            int offY=(size.Height-destHeight)/2;
            Graphics g=Graphics.FromImage((Image)b);
            g.InterpolationMode=InterpolationMode.HighQualityBicubic;
            //g.InterpolationMode = InterpolationMode.NearestNeighbor;

            g.DrawImage(imgToResize, offX, offY, destWidth, destHeight);
            g.Dispose();
            return bitmapToByteMatrix(b);
        }

        public static double[] prepareForNN(byte[,] image, int outDimension) {
            double[] retVal=new double[outDimension];
            int oneSide=(int)Math.Sqrt(outDimension);
            image=CImageUtil.resizeImage(image, new Size(outDimension, outDimension));
            for(int i=0;i<image.GetLength(0);i++) {
                for(int j=0;j<image.GetLength(1);j++) {
                    if(image[i, j]<255) {
                        int ii=i/oneSide;
                        int jj = j / oneSide;

                        retVal[ii * oneSide + jj]++;
                    }
                }
            }

            for(int i=0;i<retVal.Length;i++) {
                retVal[i]=(2*retVal[i])/outDimension-1;
            }
            return retVal;
        }

        public static byte[,] eliminateLine(byte[,] image,int eliminateFactor) {
            byte[,] newImage=(byte[,])image.Clone();
            
            //horizontal delete
            int horizontalBorder=image.GetLength(0)-1-2*eliminateFactor;
            int verticalBorder=image.GetLength(1)-1-2*eliminateFactor;
            int eliminateCounter;
            byte color;

            //vertical
            for(int i=0;i<image.GetLength(0);i++) {
                for(int j=eliminateFactor+1;j<verticalBorder;j++) {
                    eliminateCounter=0;
                    color=image[i, j-1];
                    for(int k=0;k<eliminateFactor;k++) {
                        if((image[i, j-1-k]==color)&&(color==image[i, j+k+eliminateFactor])) {
                            eliminateCounter++;
                        } else {
                            break;
                        }
                    }
                    if(eliminateCounter==eliminateFactor) {
                        for(int k=0;k<eliminateFactor;k++) {
                            newImage[i, j+k]=color;
                        }
                    }

                }
            }

            //horizontal
            for(int j=0;j<image.GetLength(1);j++) {
                for(int i=eliminateFactor+1;i<horizontalBorder;i++) {
                    eliminateCounter=0;
                    color=image[i-1, j];
                    for(int k=0;k<eliminateFactor;k++) {
                        if((image[i-1-k, j]==color)&&(color==image[i+k+eliminateFactor, j])) {
                            eliminateCounter++;
                        } else {
                            break;
                        }
                    }
                    if(eliminateCounter==eliminateFactor) {
                        for(int k=0;k<eliminateFactor;k++) {
                            newImage[i+k, j]=color;
                        }
                    }
                    
                }
            }

            

            return newImage;
        }
    }
}
